package kuti.grp5;


import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import kuti.IKuti;
import kuti.ISimulParams;

/**
 * This class manages the CyclicBarrier that deactivates the kuti after
 * DRIVER_TIME_LIMIT
 * A new thread is generated at each tick.
 * 
 * @author Marc Schorderet <marc.schorderet@edu.hefr.ch>
 * @author Vincenzo Do <vincenzo.do@edu.hefr.ch>
 */
public class BarrierManager implements Runnable {

    /*
     * The barrier 
     */
    private CyclicBarrier barrier;
    /*
     * The list of active kuti, used CopyOnWriteArrayList to avoid synchronization
     * problems.
     */
    private CopyOnWriteArrayList<IKuti> activeKutiList;
    /*
     * The world manager
     */
    private WorldManager wm;
    /*
     * Boolean to enable/disable the debugMode
     */
    private boolean debugMode = false;
    

    /**
     * Constructor of the class
     * 
     * @param barrier the barrier
     * @param activeKutiList list of all active kuti
     * @param wm the world manager
     */
    public BarrierManager(CyclicBarrier barrier, CopyOnWriteArrayList<IKuti> activeKutiList, WorldManager wm) {
        this.barrier = barrier;
        this.activeKutiList = activeKutiList;
        this.wm = wm;
    }

    /**
     * Default run method
     */
    @Override
    public void run() {
        startBarrier();
    }

    /**
     * This method starts the barrier, wait for DRIVER_TIME_LIMIT and then
     * releases the thread.
     * If the threads are all arrived at the barrier before the time limit, they 
     * are all released.
     * If the time runs out before all thread are arrived, a TimeoutException is
     * raised, when this exception is catched the deactivation of the kuti begins.
     * If the barrier gets broken a reset is put in motion.
     */
    private void startBarrier() {
        int waitTime = ISimulParams.DRIVER_TIME_LIMIT;
        try {
            if (!debugMode) {
                barrier.await(waitTime, TimeUnit.MILLISECONDS);
            } else {
                barrier.await();
            }
        } catch (TimeoutException ex) {
            //System.out.println("barrier TIMED OUT");
            synchronized (this) {
                System.out.println("disattivazzione da barriera");
                deactivateKuti();
            }
        } catch (InterruptedException ex) {
            Logger.getLogger(WorldManager.class.getName()).log(Level.SEVERE, null, ex);
        } catch (BrokenBarrierException ex) {
            resetBarrier();
        }

    }

    /**
     * This method resets the barrier, and sets the new barrier to the actuator
     */
    public void resetBarrier() {
        barrier = new CyclicBarrier(activeKutiList.size() + 2);
        for (IKuti k : activeKutiList) {
            Actuator actuator = (Actuator) ((Kuti) k).getActuator();
            actuator.setBarrier(barrier);
        }
    }

    /**
     * 
     * @return the barrier
     */
    public CyclicBarrier getBarrier() {
        return barrier;
    }

    /**
     * 
     * @return the list of active kuti
     */
    public CopyOnWriteArrayList<IKuti> getActiveKutiList() {
        return activeKutiList;
    }

    /**
     * This method sets the list of active kuti
     * @param activeKutiList 
     */
    public void setActiveKutiList(CopyOnWriteArrayList<IKuti> activeKutiList) {
        this.activeKutiList = activeKutiList;
    }

    /**
     * This method is called when a TimeoutException is raised by the barrier
     * If the thread has not replied in time it gets deactivated
     */
    public void deactivateKuti() {
        for (IKuti k : activeKutiList) {
            if (k.isActive()) {
                Actuator actuator = (Actuator) ((Kuti) k).getActuator();
                if (actuator.stepNr != wm.getStepNr()) {
                    System.out.println("Kuti disattivato: " + k.getId());
                    activeKutiList.remove(k);
                    //TODO check if needed stopThread
                    k.getDriver().stopDriverThread();
                    ((Kuti) k).setActive(false);
                }
            }
        }
    }
}